#include <windows.h>
#include <GL/glew.h>
#include <GL/gl.h>
#include <GL/glu.h>
#include "../Header/cSSAO.h"
#include "../Header/ShaderObject.h"
#include "../Header/framebufferObject.h"
#include "../Header/renderbuffer.h"
#include "../Header/glErrorUtil.h"
#include "../Header/misc.h"
#include "IL/il.h"
#include "IL/ilu.h"
#include "IL/ilut.h"

const char *IsValid(unsigned int fb);

cSSAO::cSSAO(int _w, int _h, unsigned int textureTarget)
{
	ilInit();
	iluInit();
	ilutRenderer(ILUT_OPENGL);
	ilutEnable(IL_ORIGIN_SET);
	ilGenImages(1, &rnm);
	ilBindImage(rnm);
	ILboolean rnm_loaded = ilLoadImage("Media//rnm.bmp");
	//ILboolean rnm_loaded = ilLoadImage("Media//RANDOMNORMAL.bmp");
	if(rnm_loaded == IL_FALSE){
		printf("\n\nRNM_NOT_LOADED!!! >> ");
		ILenum err = ilGetError() ;
		printf( "the error %d\n", err );
		printf( "string is %s\n", ilGetString( err ) );
	}
	rnm_w = ilGetInteger(IL_IMAGE_WIDTH);
	rnm_h = ilGetInteger(IL_IMAGE_HEIGHT);
	printf("RNM_RESOLUTION = %dx%d\n", rnm_w, rnm_h);
	rnm_data = ilGetData();
	printf("\n\nRNM_LOADED_OK >> ");

	texTarget = textureTarget;
	w = _w;
	h = _h;
	//wDown = (int)(w * 0.25);
	//hDown = (int)(h * 0.25);


	glGenFramebuffersEXT(1, &frameBuffer);
	glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, frameBuffer);

		CheckErrorsGL("cSSAO::cSSAO sceneFBO BEGIN : Creating textures");

		glGenTextures(1, &sceneTexId);
		glBindTexture(texTarget, sceneTexId);
		glTexImage2D(texTarget, 0, GL_RGBA32F_ARB, w, h, 0, GL_RGBA, GL_FLOAT, NULL);
		glTexParameteri(texTarget, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
		glTexParameteri(texTarget, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
		glTexParameteri(texTarget, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
		glTexParameteri(texTarget, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);

		CheckErrorsGL("\ncSSAO::cSSAO sceneFBO END : Creating textures 0\n");

		// Spec: http://www.opengl.org/registry/specs/EXT/packed_depth_stencil.txt
		glGenTextures(1, &depthTexture);
		glBindTexture(texTarget, depthTexture);
		glTexImage2D(texTarget, 0, GL_DEPTH24_STENCIL8_EXT, w, h, 0, GL_DEPTH_STENCIL_EXT, GL_UNSIGNED_INT_24_8_EXT, NULL);
		glTexParameteri(texTarget, GL_TEXTURE_COMPARE_MODE, GL_NONE);
		glTexParameteri(texTarget, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
		glTexParameteri(texTarget, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
		glTexParameteri(texTarget, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
		glTexParameteri(texTarget, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
		//-------------------------

		glGenTextures(1, &depthTexId);
		glBindTexture(texTarget, depthTexId);
		glTexImage2D(texTarget, 0, GL_RGBA32F_ARB, w, h, 0, GL_RGBA, GL_FLOAT, NULL);
		glTexParameteri(texTarget, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
		glTexParameteri(texTarget, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
		glTexParameteri(texTarget, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
		glTexParameteri(texTarget, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);

		glGenTextures(1, &normalTexId);
		glBindTexture(texTarget, normalTexId);
		glTexImage2D(texTarget, 0, GL_RGBA32F_ARB, w, h, 0, GL_RGBA, GL_FLOAT, NULL);
		glTexParameteri(texTarget, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
		glTexParameteri(texTarget, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
		glTexParameteri(texTarget, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
		glTexParameteri(texTarget, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);

		CheckErrorsGL("\ncSSAO::cSSAO sceneFBO END : Creating textures 1\n");
		glGenTextures(1, &ssaoTexId);
		glBindTexture(texTarget, ssaoTexId);
		glTexImage2D(texTarget, 0, GL_RGBA32F_ARB, w, h, 0, GL_RGBA, GL_FLOAT, NULL);
		glTexParameteri(texTarget, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
		glTexParameteri(texTarget, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
		glTexParameteri(texTarget, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
		glTexParameteri(texTarget, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);

		glGenTextures(1, &rnmTexId);
		glBindTexture(texTarget, rnmTexId);
		glTexImage2D(texTarget, 0, GL_RGB, rnm_w, rnm_h, 0, GL_RGB, GL_UNSIGNED_BYTE, rnm_data);
		glTexParameteri(texTarget, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
		glTexParameteri(texTarget, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
		glTexParameteri(texTarget, GL_TEXTURE_WRAP_S, GL_REPEAT);
		glTexParameteri(texTarget, GL_TEXTURE_WRAP_T, GL_REPEAT);		
		
		//glGenerateMipmapEXT(texTarget);
		//Doc: http://www.opengl.org/sdk/docs/man/xhtml/gluBuild2DMipmaps.xml
		//gluBuild2DMipmaps(texTarget, GL_RGB, rnm_w, rnm_h, GL_RGB, GL_UNSIGNED_BYTE, rnm_data);

		CheckErrorsGL("\ncSSAO::cSSAO sceneFBO END : Creating textures 2\n");
		CheckErrorsGL("\ncSSAO::cSSAO BEGIN : Configuring FBO\n");

		//Attach 2D texture to this FBO
		glFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0_EXT, texTarget, sceneTexId,  0/*mipmap level*/);
		glFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT1_EXT, texTarget, normalTexId, 0/*mipmap level*/);
		glFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT2_EXT, texTarget, depthTexId,  0/*mipmap level*/);
		glFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT3_EXT, texTarget, ssaoTexId,   0/*mipmap level*/);
		//-------------------------
		//Attach depth texture to FBO
		glFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT, GL_DEPTH_ATTACHMENT_EXT,   texTarget, depthTexture, 0/*mipmap level*/);
		glFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT, GL_STENCIL_ATTACHMENT_EXT, texTarget, depthTexture, 0/*mipmap level*/);
		//-------------------------

	glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, 0);
	printf(IsValid(frameBuffer));
	CheckErrorsGL("\ncSSAO::cSSAO END : Configuring FBO\n");

	displayTexRect = new ShaderObject("shaders/passthruTexRectFragment.glsl");
	displayTexRect->activate();
		displayTexRect->setUniformi("tex",0);
	displayTexRect->deactivate();

	displayTex2D = new ShaderObject("shaders/fx/passthruTexVertex.glsl", "shaders/fx/passthruTexFragment.glsl");
	displayTex2D->activate();
		displayTex2D->setUniformi("diffuseTex",0);
	displayTex2D->deactivate();

	depth = new ShaderObject("shaders/ssao/depthVertex.glsl", "shaders/ssao/depthFragment.glsl");
	depth->activate();
		depth->setUniformi("depthImg", 0);
	depth->deactivate();

	normal = new ShaderObject("shaders/ssao/normalVertex.glsl", "shaders/ssao/normalFragment.glsl");
	normal->activate();
		normal->setUniformi("depthImg", 0);
	normal->deactivate();

	ssao = new ShaderObject("shaders/ssao/ssaoVertex.glsl", "shaders/ssao/ssaoFragment.glsl");
	ssao->activate();
		ssao->setUniformi("rnm", 0);
		ssao->setUniformi("normalMap", 1);
	ssao->deactivate();
	
	composition = new ShaderObject ("shaders/ssao/multVertex.glsl", "shaders/ssao/multFragment.glsl");
	composition->activate();
		composition->setUniformi("scene", 0);
		composition->setUniformi("ssaoScene", 1);
	composition->deactivate();
}

cSSAO::~cSSAO()
{
	FREE_MEMORY(displayTexRect);
	FREE_MEMORY(displayTex2D);
	FREE_MEMORY(downSampleFBO);
	FREE_MEMORY(downSampleShader);
	FREE_MEMORY(normal);
	FREE_MEMORY(ssao);
	FREE_MEMORY(depth);
	FREE_MEMORY(composition);

	FREE_TEXTURE(sceneTexId);
	FREE_TEXTURE(downSampleTexId);
	FREE_TEXTURE(normalTexId);
	FREE_TEXTURE(depthTexId);
	FREE_TEXTURE(depthTexture);
	FREE_TEXTURE(ssaoTexId);
	ilDeleteImages(1, &rnm);
	free(rnm_data); 
	rnm_data = NULL;
	
}

void cSSAO::setFBOProjection (int w, int h)
{
	glMatrixMode(GL_PROJECTION);
	glPushMatrix();
	glLoadIdentity();
	glGetIntegerv(GL_VIEWPORT, vp);
	glViewport (0,0, w, h);
	glOrtho(0.0, w, 0.0, h, -1,1);
	glMatrixMode (GL_MODELVIEW);
	glPushMatrix ();
	glLoadIdentity ();
}

void cSSAO::restoreProjection ()
{
	glPopMatrix ();
	glMatrixMode(GL_PROJECTION);
	glViewport (vp[0],vp[1], vp[2], vp[3]);
	glPopMatrix();
	glMatrixMode (GL_MODELVIEW);
}

void cSSAO::setFBORProjection (int w, int h)
{
    glMatrixMode(GL_PROJECTION);
    glPushMatrix();
    glLoadIdentity();
	glOrtho(0.0, w, 0.0, h, -1.0, 1.0);
    glMatrixMode(GL_MODELVIEW);
    glPushMatrix();
    glLoadIdentity();
}

void cSSAO::restoreRProjection ()
{
	glPopMatrix();
    glMatrixMode(GL_PROJECTION);
    glPopMatrix();
    glMatrixMode(GL_MODELVIEW);
}

void cSSAO::renderToDepth()
{
	// Bind To The Depth Texture
	glBindTexture(GL_TEXTURE_2D,depthTexId);
	// Copy Our ViewPort To The Depth Texture (No border)
	glCopyTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 0, 0, w, h, 0);
}

void cSSAO::renderToNormal()
{
	// Bind To The Normal Texture
	glBindTexture(GL_TEXTURE_2D,normalTexId);
	// Copy Our ViewPort To The Normal Texture (No border)
	glCopyTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 0, 0, w, h, 0);
}

void cSSAO::renderToSSAO()
{
	// Bind To The SSAO Texture
	glBindTexture(GL_TEXTURE_2D,ssaoTexId);
	// Copy Our ViewPort To The SSAO Texture (No border)
	glCopyTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 0, 0, w, h, 0);
}

const char *IsValid(unsigned int fb){

	// Fuente: http://www.gamedev.net/community/forums/topic.asp?topic_id=364174
	char *m_sLastError = "\nFB_OK\n";
	bool isOK = false;
	GLenum status;
	glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, fb);
	status = glCheckFramebufferStatusEXT(GL_FRAMEBUFFER_EXT);
	switch(status) 
	{                                          
	case GL_FRAMEBUFFER_COMPLETE_EXT: // Everything's OK
		isOK = true;
		break;
	case GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT_EXT:
		m_sLastError = "\nGL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT_EXT\n";
		isOK = false;
		break;
	case GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT_EXT:
		m_sLastError = "\nGL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT_EXT\n";
		isOK = false;
		break;
	//case GL_FRAMEBUFFER_INCOMPLETE_DUPLICATE_ATTACHMENT_EXT:
	//	m_sLastError = "GL_FRAMEBUFFER_INCOMPLETE_DUPLICATE_ATTACHMENT_EXT";
	//	isOK = false;
	//	break;
	case GL_FRAMEBUFFER_INCOMPLETE_DIMENSIONS_EXT:
		m_sLastError = "\nGL_FRAMEBUFFER_INCOMPLETE_DIMENSIONS_EXT\n";
		isOK = false;
		break;
	case GL_FRAMEBUFFER_INCOMPLETE_FORMATS_EXT:
		m_sLastError = "\nGL_FRAMEBUFFER_INCOMPLETE_FORMATS_EXT\n";
		isOK = false;
		break;
	case GL_FRAMEBUFFER_INCOMPLETE_DRAW_BUFFER_EXT:
		m_sLastError = "\nGL_FRAMEBUFFER_INCOMPLETE_DRAW_BUFFER_EXT\n";
		isOK = false;
		break;
	case GL_FRAMEBUFFER_INCOMPLETE_READ_BUFFER_EXT:
		m_sLastError = "\nGL_FRAMEBUFFER_INCOMPLETE_READ_BUFFER_EXT\n";
		isOK = false;
		break;
	case GL_FRAMEBUFFER_UNSUPPORTED_EXT:
		m_sLastError = "\nGL_FRAMEBUFFER_UNSUPPORTED_EXT\n";
		isOK = false;
		break;
	//case GL_FRAMEBUFFER_STATUS_ERROR_EXT:
	//	m_sLastError = "GL_FRAMEBUFFER_STATUS_ERROR_EXT\n";
	//	isOK = false;
	//	break;
	default:
		m_sLastError = "Unknown ERROR";
		isOK = false;
	};

	return(m_sLastError);
}
